/*****************************************************************************
* Project:
* All project
* Copyright(C) 2020-2021 Shenju Co.Ltd, All rights reserved.
* Description:
* All code is genrated by yangyang
* Author:
* yangyang
****************************************************************************/

#include "pcm.h"

#include <stdio.h>
#include <windows.h>
#include <mmsystem.h>
#include <malloc.h>
#include <assert.h>

#define printf

#define MAX_HDRCOUNT 20

typedef struct {
    enum pcm_flag flag;
    struct pcm_config cfg;
    WAVEFORMATEX waveformat;
    WAVEHDR *pWaveHdr[MAX_HDRCOUNT];
    int nhdr;

    //read
    HWAVEIN hWaveIn;

    //write
    HWAVEOUT       hWaveOut;
    HANDLE         wait;
} WIN32AUDIO_HANDLE;

void CALLBACK WaveCallback(HWAVEOUT hWave, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
    hWave = hWave;
    dw1 = dw1;
    dw2 = dw2;
    if (uMsg == WOM_DONE || uMsg == WIM_DATA) {
        WIN32AUDIO_HANDLE *p = (WIN32AUDIO_HANDLE *)(dwUser);
        ReleaseSemaphore(p->wait, 1, NULL);
    }
}

void *pcm_open(uint32_t snd_card_id, enum pcm_flag flag, const struct pcm_config *config)
{
    snd_card_id = snd_card_id;
    uint32_t i;
    WIN32AUDIO_HANDLE* p;

    p = (WIN32AUDIO_HANDLE*)calloc(1, sizeof(WIN32AUDIO_HANDLE));
    if(!p){
        printf("%s\n", "[os win32:]OpenAudioCodec alloc error.");
        return 0;
    }
    p->cfg = *config;
    p->flag = flag;
    p->waveformat.cbSize = sizeof(WAVEFORMATEX);
    p->waveformat.wFormatTag = WAVE_FORMAT_PCM;
    p->waveformat.nChannels = (WORD)p->cfg.ch;
    p->waveformat.nSamplesPerSec = p->cfg.rate;
    p->waveformat.wBitsPerSample = (WORD)p->cfg.sample_size*8;
    p->waveformat.nBlockAlign = p->waveformat.nChannels * p->waveformat.wBitsPerSample / 8;
    p->waveformat.nAvgBytesPerSec = p->waveformat.nSamplesPerSec * p->waveformat.nBlockAlign;

    if (PCM_IN == flag)
    {
        int error;

        // be careful for effect.
        //p->cfg.channelNum = cfg->channelNum = 1;

        p->wait = CreateSemaphore(NULL, 0, MAX_HDRCOUNT, NULL);
        error = waveInOpen(&p->hWaveIn, WAVE_MAPPER, &p->waveformat, (DWORD_PTR)WaveCallback, (DWORD_PTR)p, CALLBACK_FUNCTION);
        if (error != MMSYSERR_NOERROR) {
            free(p);
            printf("[os win32:] waveInOpen error. ");
            return 0;
        }

        for (i = 0; i < MAX_HDRCOUNT; i++) {
            p->pWaveHdr[i] = (WAVEHDR*)calloc(1, p->cfg.period_size*p->cfg.sample_size*p->cfg.ch + sizeof(WAVEHDR));
            assert((int)(p->pWaveHdr[i]));

            p->pWaveHdr[i]->dwBufferLength = p->cfg.period_size * p->cfg.sample_size * p->cfg.ch;
            p->pWaveHdr[i]->lpData = (LPSTR)(p->pWaveHdr[i] + 1);
            error = waveInPrepareHeader(p->hWaveIn, p->pWaveHdr[i], sizeof(WAVEHDR));
            if (error != MMSYSERR_NOERROR) {
                free(p);
                printf("[os win32:]waveInPrepareHeader error ");
                return 0;
            }
            error = waveInAddBuffer(p->hWaveIn, p->pWaveHdr[i], sizeof(WAVEHDR));
            if (error != MMSYSERR_NOERROR) {
                free(p);
                printf("[os win32:]waveInAddBuffer error. ");
                return 0;
            }
        }

        error = waveInStart(p->hWaveIn);
        if (error != MMSYSERR_NOERROR) {
            free(p);
            printf("[os win32:]waveInStart error. ");
            return 0;
        }

        p->nhdr = 0;
    }
    else
    {
        p->wait = CreateSemaphore(NULL, MAX_HDRCOUNT, MAX_HDRCOUNT, NULL);
        waveOutOpen(&p->hWaveOut, WAVE_MAPPER, &p->waveformat, (DWORD_PTR)WaveCallback, (DWORD_PTR)p, CALLBACK_FUNCTION);
        for (i = 0; i < MAX_HDRCOUNT; i++) {
            p->pWaveHdr[i] = (WAVEHDR*)calloc(1, p->cfg.period_size * p->cfg.sample_size * p->cfg.ch + sizeof(WAVEHDR));
            assert((int)(p->pWaveHdr[i]));
            p->pWaveHdr[i]->dwBufferLength = p->cfg.period_size * p->cfg.sample_size * p->cfg.ch;
            p->pWaveHdr[i]->lpData = (LPSTR)(p->pWaveHdr[i] + 1);
            p->pWaveHdr[i]->dwFlags = 0L;
            p->pWaveHdr[i]->dwLoops = 1L;
            p->pWaveHdr[i]->dwFlags |= WHDR_DONE;
            // waveOutPrepareHeader(p->hWaveOut, p->pWaveHdr[i], sizeof(WAVEHDR));
        }
    }
    return p;
}

int pcm_close(void *pcm)
{
    WIN32AUDIO_HANDLE* p = (WIN32AUDIO_HANDLE*)pcm;
    if (PCM_IN == p->flag)
    {
        int i;
        int error = waveInStop(p->hWaveIn);
        if (error != MMSYSERR_NOERROR) {
            printf("[os win32:]waveInStop error. ");
            return -1;
        }

        error = waveInReset(p->hWaveIn);
        if (error != MMSYSERR_NOERROR) {
            printf("[os win32:]waveInReset error. ");
            return -1;
        }

        for (i = 0; i < MAX_HDRCOUNT; i++) {
            error = waveInUnprepareHeader(p->hWaveIn, p->pWaveHdr[i], sizeof(WAVEHDR));
            if (error != MMSYSERR_NOERROR) {
                printf("[os win32:]waveInUnprepareHeader. ");
                return -1;
            }
            free(p->pWaveHdr[i]);
        }

        error = waveInClose(p->hWaveIn);
        if (error != MMSYSERR_NOERROR) {
            printf("[os win32:]waveInClose. ");
            return -1;
        }
    }
    else
    {
        int i;
        for (i = 0; i < MAX_HDRCOUNT; i++)
        {
            if (p->pWaveHdr[i])
            {
                while (((p->pWaveHdr[i]->dwFlags) & WHDR_DONE) == 0)
                    Sleep(15);

                waveOutUnprepareHeader(p->hWaveOut, p->pWaveHdr[i], sizeof(WAVEHDR));
                free(p->pWaveHdr[i]);
            }
        }
        waveOutReset(p->hWaveOut);
        waveOutClose(p->hWaveOut);
        CloseHandle(p->wait);
    }

    free(p);
    return 0;
}

int pcm_write(void *pcm, const void *data, uint32_t samples)
{
    WIN32AUDIO_HANDLE* p = (WIN32AUDIO_HANDLE*)pcm;

    if (samples > p->cfg.period_size)
        samples = p->cfg.period_size;

    WaitForSingleObject(p->wait, INFINITE);

    memset(p->pWaveHdr[p->nhdr]->lpData, 0, p->cfg.period_size*p->cfg.sample_size);
    memcpy(p->pWaveHdr[p->nhdr]->lpData, data, samples*p->cfg.sample_size*p->cfg.ch);
    p->pWaveHdr[p->nhdr]->dwBufferLength = samples * p->cfg.sample_size * p->cfg.ch;
    waveOutPrepareHeader(p->hWaveOut, p->pWaveHdr[p->nhdr], sizeof(WAVEHDR));
    waveOutWrite(p->hWaveOut, p->pWaveHdr[p->nhdr], sizeof(WAVEHDR));
    waveOutUnprepareHeader(p->hWaveOut, p->pWaveHdr[p->nhdr], sizeof(WAVEHDR));
    p->nhdr = (p->nhdr + 1) % MAX_HDRCOUNT;
    return samples;
}

int pcm_read(void *pcm, void *data, uint32_t samples)
{
    int error;
    WIN32AUDIO_HANDLE* p = (WIN32AUDIO_HANDLE*)pcm;
    size_t block_size = p->cfg.period_size * p->cfg.sample_size * p->cfg.ch;
    if (samples > block_size)
        samples = block_size;

    WaitForSingleObject(p->wait, INFINITE);

    memcpy(data, p->pWaveHdr[p->nhdr]->lpData, samples * p->cfg.sample_size * p->cfg.ch);
    error = waveInAddBuffer(p->hWaveIn, p->pWaveHdr[p->nhdr], sizeof(WAVEHDR));
    if (error != MMSYSERR_NOERROR) {
        printf("[os win32:]waveInAddBuffer error. ");
        return 0;
    }
    p->nhdr = (p->nhdr + 1) % MAX_HDRCOUNT;
    return samples;
}

